/**\file gr_streamdata.h Streamdata.
*
*/
#pragma once

#include "gr_streamsignature.h"
#include "gr_aligned_allocator.h"
#include "gr_containers.h"
#include "gr_shims.h"
#include "gr_algorithms.h"

#include<sstream>
#include<typeinfo>
#include<unordered_map>
#include<list>
#include<memory>

namespace gr{

enum GeometryIndexLayout
{
    TRIANGLES, 
    LINES,
    LINE_STRIP,
    LINE_LOOP
};

/** Stores a contiguous array of samples composed of a sequence of one or more
  * specific types.*/
class StreamData
{
public:

    StreamData();
    void setSignature(StreamSignature& signature);
    void setData(uint8_t* buffer, size_t size);
    void free();
    size_t size();
    uint8_t* data();
    size_t sampleSize();    //> Size of the sample
    size_t sampleStride();  //> Number of bytes between the beginnings of consecutive samples
    StreamSignature& signature();

    /** Get typed handle to specified channel of stream.
     *  Raise exception if channel not found or type mismatch. At this point
     *  We compare only the sizes of the mapped type and data in the stream.
     *  The user must make sure that the cast makes sense for her.
     *  It's a completely valid use case to pack numeric data in some specific
     *  format inside a different type so we can't decide what
     *  is semantically correct for the user here.
     */
    UnsafeView channel(const std::string& name)
    {
        UnsafeView::offset_size offset = 0;
        if(const size_t* offsetptr = signature_.offsetOfChannel(name))
        {
            offset = *offsetptr;
        }
        else
        {
            std::string msg = "Name " + name + " not found in StreamData";
            throw std::runtime_error(msg);
        }

        UnsafeView::sample_size sampleSize = signature_.sizeOfChannel(name);

        UnsafeView handle(&data_[0], offset, sampleSize, size());
        return handle;
    }

private: 
    StreamSignature signature_;
    AlignedArray<uint8_t> data_;
};

/** Index on StreamData to represent it in forms of geometric entities (faces, edges, points).*/
class StreamGeometryIndexData
{
public:
    StreamGeometryIndexData();
    void setData(uint16_t* indices,size_t count );
    size_t indexCount();
    uint16_t* data();
    size_t size();
    void free();
    GeometryIndexLayout layout();
    void layout(GeometryIndexLayout l);
private:
    std::vector<uint16_t> indices_;
    GeometryIndexLayout layout_;
};

/** Composition of a StreamData and GeometryIndexData. This owns its pointer
 *  to streamdata and indexdata since this class is designed for vertex and
 *  indexbuffer management it does not make really sense to 
 *          a) recycle the resources or
 *          b) map several sources to them same buffer.
 */
class StreamDataComposition
{
public:
    typedef std::unique_ptr<StreamData>                           streamdataptr;
    typedef std::unique_ptr<StreamGeometryIndexData>              indexdataptr;
    typedef std::unordered_map<GeometryIndexLayout, indexdataptr> indexmap;

    class Segment
    {
    public:
        streamdataptr streamdataptr_;
        indexmap      indexmap_;
        Segment(){}
        StreamGeometryIndexData* addIndexData(GeometryIndexLayout layout)
        {
            if(!hasKey(indexmap_, layout))
            {
                auto indices = new StreamGeometryIndexData();
                indices->layout(layout);
                indexmap_[layout] = indexdataptr(indices);
            }
            return indexmap_[layout].get();
        }

        void init(StreamSignature& signature, GeometryIndexLayout layout)
        {
            auto stream = new StreamData();
            stream->setSignature(signature);
            streamdataptr_.reset(stream);
            addIndexData(layout);
        }

        StreamData* streamData(){return streamdataptr_.get();}

        StreamGeometryIndexData* indexData(GeometryIndexLayout layout)
        {
            indexdataptr* ptr(tryGetValue(indexmap_, layout));
            return ptr ? ptr->get() : nullptr;
        }
    };

    typedef std::unique_ptr<Segment> SegmentPtr;
    typedef std::map<integer_key, SegmentPtr> SegmentMap;

    KeyWarehouse     keyCollection_;//> Key generator.
    SegmentMap       segments_;
    size_t           maxDataCount_; //> Maximum number of elements per segment.
    StreamSignature& signature_;    //> Signature for contained streams.

    StreamDataComposition(StreamSignature& signature, size_t maxDataCount = 1024):maxDataCount_(maxDataCount), signature_(signature)
    {}

    integer_key pushSegment(GeometryIndexLayout layout)
    {
        integer_key key = keyCollection_.newKey();
        segments_[key] = SegmentPtr(new Segment()); 
        segments_[key]->init(signature_, layout); 
        return key;
    }

    StreamSignature& signature(){return signature_;}

    StreamData*              streamByKey(integer_key key){return segments_[key]->streamData();}
    StreamGeometryIndexData* indexByKey(integer_key key, GeometryIndexLayout layout){return segments_[key]->indexData(layout);}
    StreamGeometryIndexData* addIndexToKey(integer_key key, GeometryIndexLayout layout){return segments_[key]->addIndexData(layout);}

    // Insert data from UnsafeView to stream.
    bool insert(const std::string& channelName, UnsafeView& source)
    {
        //TODO
        return false;
    }

    struct IndexInserter
    {
        // push indices; when index is full start new index
    };

    // Return inserter for specified layout
    IndexInserter indexInserter(GeometryIndexLayout layout)
    {
        IndexInserter i;
        return i;
    }

};

StreamData* newStreamData();
StreamGeometryIndexData* newStreamGeometryIndexData();



}
